﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.ServiceModel.Channels;
using System.ServiceModel.Configuration;
using System.ServiceModel.Description;
using System.ServiceModel.Discovery;
using System.Xml;
using System.Linq;
using System.Xml.Linq;
using Devtm.ServiceModel.Configurations;
using Devtm.ServiceModel.Criterias;
using Devtm.ServiceModel.Discovery;
using Devtm.ServiceModel.Proxy;
using Devtm.ServiceModel.Resolver.Connectors;
using System.Linq.Dynamic;

namespace Devtm.ServiceModel.Resolver
{

    /// <summary>
    /// Allows resolve the endpoint with a Directory Discovery
    /// </summary>
    public class ServiceResolver : IServiceResolver
    {

        private List<IServiceConnector> _list = new List<IServiceConnector>();
        private ServiceCriteria criterias;

        public ServiceResolver(ProxyConfiguration configuration)
        {

            ConnectorFilter = e => { return true; };

            this.Configuration = configuration;
            criterias = new ServiceCriteria(this.Contract = configuration.Interface, this.Configuration.Factory.Probes);
        }


        /// <summary>
        /// Refresh the list of endpoint
        /// </summary>
        /// <returns></returns>
        public bool Refresh()
        {

            var findResponse = criterias.FindResponse();

            if (findResponse != null)
            {
                _list.Clear();

                if (findResponse.Endpoints.Count > 0)
                    foreach (EndpointDiscoveryMetadata endpoint in findResponse.Endpoints)
                        Add(endpoint);

            }

            return _list.Count != 0;

        }


        public Func<IServiceConnector, bool> ConnectorFilter { get; set; }
        public ModeTurnOver Mode { get; set; }
        public bool IsEmpty { get { return _list.Count == 0; } }
        public ProxyConfiguration Configuration { get; set; }
        public Type Contract { get; set; }
        public IEnumerator<IServiceConnector> GetEnumerator() { return _list.GetEnumerator(); }
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return _list.GetEnumerator(); }

        private int index = 0;

        /// <summary>
        /// Return a ServiceConnector instance
        /// 
        /// </summary>
        public IServiceConnector Connector
        {
            get
            {

                IServiceConnector retour = null;

                switch (Mode)
                {
                    case ModeTurnOver.First:
                        retour = _list.FirstOrDefault();
                        break;

                    case ModeTurnOver.Rule:
                        
                        // Rule

                        break;

                    case ModeTurnOver.RobinRound:
                        retour = _list[index];
                        index++;

                        if (index == _list.Count)
                            index = 0;

                        break;

                }




                return retour;
            }
        }
    

        private void Add(System.ServiceModel.Discovery.EndpointDiscoveryMetadata endpoint)
        {

            foreach (var x1 in endpoint.Extensions)
            {

                Extension ext = Extension.UnSerialize(x1);

                if (ext.Mode == Constantes.ModeContainer.Meta)
                {

                    XElement x = ext.Data;
                    if (x != null)
                    {
                        string txt = x.Value;
                        if (!string.IsNullOrEmpty(txt))
                        {

                            // Deserialize
                            StringReader sReader = new StringReader(txt);
                            XmlReader reader = XmlReader.Create(sReader);
                            MetadataSet metadataDocs = MetadataSet.ReadFrom(reader);
                            WsdlImporter importer = new WsdlImporter(metadataDocs);


                            IEnumerable<ContractDescription> contracts =
                                new System.Collections.ObjectModel.Collection<ContractDescription> { ContractDescription.GetContract(Contract) };

                            foreach (ContractDescription description in contracts)
                                importer.KnownContracts.Add(GetQualifiedName(description), description);


                                var list = importer.ImportAllEndpoints();

                                foreach (var item in list)
                                    Add(new ServiceEndpointConnector(this, item));
                                
                            
                        }
                    }

                }
                else if (ext.Mode == Constantes.ModeContainer.Service)
                {

                    XElement x = ext.Data;
                    if (x != null)
                    {
                        if (x.Name == "urls")
                        {                            
                            var list = Helpers.BindingHelper.InitializeUrls(Contract, x).ToList();
                            foreach (var item in list)
                            {
                                IServiceConnector cc = new ServiceEndpointConnector(this, item);
                                if (!_list.Contains(cc))
                                    Add(cc);
                            }
                        }
                        else if (x.Name == "system.serviceModel")
                        {
                            XElement endpoint1 = x.Element("client").Element("endpoint");
                            if (endpoint1 != null)
                            {

                                var bindingName = endpoint1.Attribute("binding");
                                var bindingConfigurationName = endpoint1.Attribute("bindingConfiguration");

                                if (bindingConfigurationName == null)
                                    bindingConfigurationName = new XAttribute("bindingConfiguration", "");

                                XElement bindingContent = x.Element("bindings");
                                if (bindingContent == null)
                                {

                                    XElement e = null;
                                    bindingContent = new XElement
                                        (
                                          "bindings",
                                          e = new XElement
                                              (bindingName.Value)
                                        );

                                    if (endpoint1.Attribute("bindingConfiguration") != null)
                                        e.Add(new XElement("binding", new XAttribute("name", bindingConfigurationName)));

                                }

                                var binding = CreateBinding
                                (
                                  bindingName.Value,
                                  bindingConfigurationName.Value,
                                  bindingContent.ToString()
                                );

                                IServiceConnector cc = new EndpointAddressConnector(this, endpoint.Address, binding);
                                if (!_list.Contains(cc))
                                    Add(cc);

                            }
                        }
                    }

                }


            }

        }

        private void Add(IServiceConnector serviceEndpointConnector)
        {
            if (ConnectorFilter == null || ConnectorFilter(serviceEndpointConnector))
                _list.Add(serviceEndpointConnector);
        }


        internal static XmlQualifiedName GetQualifiedName(ContractDescription contract)
        {
            return new XmlQualifiedName(contract.Name, contract.Namespace);
        }

        /// <summary>
        /// Configures the binding for the selected endpoint
        /// </summary>
        /// <param name="bindingName"></param>
        /// <param name="group"></param>
        /// <returns></returns>
        private Binding CreateBinding(string bindingName, string bindingConfiguration, string bindingContent)
        {

            Binding b = null;

            // On récupère la section bindings
            BindingsSection conf = System.Configuration.ConfigurationManager.GetSection(@"system.serviceModel/bindings") as BindingsSection;

            // On créer un objet pour l'assignement des propriétés
            CustomBindingsSection section = new CustomBindingsSection();
            using (StringWriter sWriter = new StringWriter())
            {
                StringReader sReader = new StringReader(bindingContent);
                XmlReader reader = XmlReader.Create(sReader);
                section.ReadXml(reader);
            }


            foreach (BindingCollectionElement item in conf.BindingCollections)

                if (item.BindingName == bindingName)
                {

                    b = Activator.CreateInstance(item.BindingType) as Binding;

                    if (!string.IsNullOrEmpty(bindingConfiguration))
                        foreach (var i2 in item.ConfiguredBindings)
                            if (i2.Name == bindingConfiguration)
                            {
                                i2.ApplyConfiguration(b);
                                break;
                            }

                    break;

                }

            return b;

        }


    }

}
